package com.open.source.javassist;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import org.apache.commons.collections4.CollectionUtils;

/**
 * @author ZonLen since on 2022/3/24 上午12:15
 */
public class MethodMaker {

  private final ClassPool classPool;
  private final CtClass currentClass;

  private String methodName;
  private ModifierAccess modifierAccess = ModifierAccess.PUBLIC;
  private CtClass returnType;
  private CtClass[] parameters;
  private CtClass[] exceptions;
  private String body;

  private final List<AnnotationsAttribute> annotations = new ArrayList<>();

  private MethodMaker(ClassPool classPool, CtClass currentClass) {
    this.classPool = classPool;
    this.currentClass = currentClass;
  }

  public static MethodMaker instance(ClassPool classPool, CtClass currentClass) {
    return new MethodMaker(classPool, currentClass);
  }

  public MethodMaker methodName(String methodName) {
    this.methodName = methodName;
    return this;
  }

  public MethodMaker modifierAccess(ModifierAccess modifierAccess) {
    this.modifierAccess = modifierAccess;
    return this;
  }

  public MethodMaker returnType(Class<?> returnType) {
    try {
      if (null == returnType) {
        this.returnType = CtClass.voidType;
      } else {
        this.returnType = classPool.get(returnType.getName());
      }
    } catch (NotFoundException e) {
      throw new RuntimeException(e);
    }
    return this;
  }

  public MethodMaker parameters(Class<?>... parameters) {
    if (null != parameters) {
      this.parameters = new CtClass[parameters.length];
      for (int i = 0; i < parameters.length; i++) {
        try {
          this.parameters[i] = classPool.get(parameters[i].getName());
        } catch (NotFoundException e) {
          throw new RuntimeException(e);
        }
      }
    }
    return this;
  }

  public MethodMaker exceptions(Class<?>... exceptions) {
    if (null != exceptions) {
      this.exceptions = new CtClass[exceptions.length];
      for (int i = 0; i < exceptions.length; i++) {
        try {
          this.exceptions[i] = classPool.get(exceptions[i].getName());
        } catch (NotFoundException e) {
          throw new RuntimeException(e);
        }
      }
    }
    return this;
  }

  public MethodMaker body(String body) {
    this.body = body;
    return this;
  }

  public MethodMaker _annotation(Function<AnnotationMaker, AnnotationsAttribute> function) {
    final AnnotationMaker annotation = AnnotationMaker
        .instance(currentClass.getClassFile().getConstPool());
    annotations.add(function.apply(annotation));
    return this;
  }

  public CtMethod buildMethod() {
    final CtMethod method;
    try {
      method = CtNewMethod
          .make(modifierAccess.modifierAccess(), returnType, methodName,
              parameters, exceptions, body, currentClass);
    } catch (CannotCompileException e) {
      throw new RuntimeException(e);
    }
    if (CollectionUtils.isNotEmpty(annotations)) {
      for (AnnotationsAttribute a : annotations) {
        method.getMethodInfo().addAttribute(a);
      }
    }
    return method;
  }
}
